package com.szm.sentinelx.slots.defaults.degrade;

import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.property.DynamicSentinelProperty;
import com.alibaba.csp.sentinel.property.PropertyListener;
import com.alibaba.csp.sentinel.property.SentinelProperty;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreaker;
import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.ExceptionCircuitBreaker;
import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.ResponseTimeCircuitBreaker;
import com.alibaba.csp.sentinel.util.AssertUtil;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.szm.sentinelx.slots.config.SentinelXConfig;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.StampedLock;


/**
 * @author: shaozeming
 * @date: 2022/8/12 23:04
 * @description: 对于使用中的CircuitBreaker需要保持唯一，重新创建意味着内部的短路数据将会被重置
 **/
public class DefaultDegradeManager {

    private static volatile Map<String, List<CircuitBreaker>> circuitBreakerBack = new ConcurrentHashMap<>();
    private static volatile Set<DegradeRule> degradeRules = new HashSet<>();

    private static final RulePropertyListener LISTENER = new RulePropertyListener();
    private static SentinelProperty<List<DegradeRule>> currentProperty
            = new DynamicSentinelProperty<>();


    static final StampedLock lock = new StampedLock();

    static {
        currentProperty.addListener(LISTENER);
    }

    /**
     * Listen to the {@link SentinelProperty} for {@link DegradeRule}s. The property is the source
     * of {@link DegradeRule}s. Degrade rules can also be set by {@link #loadRules(List)} directly.
     *
     * @param property the property to listen.
     */
    public static void register2Property(SentinelProperty<List<DegradeRule>> property) {
        AssertUtil.notNull(property, "property cannot be null");
        synchronized (LISTENER) {
            RecordLog.info("[DefaultDegradeManager] Registering new property to degrade rule manager");
            currentProperty.removeListener(LISTENER);
            property.addListener(LISTENER);
            currentProperty = property;
        }
    }

    static List<CircuitBreaker> getCircuitBreakers(String name) {
        List<CircuitBreaker> circuitBreakerList = circuitBreakerBack.get(name);
        if (circuitBreakerList != null) {
           return circuitBreakerList;
        }
        circuitBreakerList = initCircuitBreaker(name);
        return circuitBreakerList;
    }


    private static List<CircuitBreaker>  initCircuitBreaker(String name){
        List<CircuitBreaker> circuitBreakerList = new ArrayList<>();
        long stamp = lock.tryReadLock();
        if(stamp<= 0){
            return circuitBreakerList;
        }
        try {
            for (DegradeRule degradeRule : degradeRules) {
                circuitBreakerList.add(createNewOrReuseCircuitBreaker(name,degradeRule,null));
            }
            circuitBreakerBack.put(name, circuitBreakerList);
        }finally {
            lock.unlockRead(stamp);
        }
        return circuitBreakerList;
    }


    /**
     * Load {@link DegradeRule}s, former rules will be replaced.
     *
     * @param rules new rules to load.
     */
    public static void loadRules(List<DegradeRule> rules) {
        try {
            currentProperty.updateValue(rules);
        } catch (Throwable e) {
            RecordLog.error("[DefaultDegradeManager] Unexpected error when loading degrade rules", e);
        }
    }


    /**
     * Create a circuit breaker instance from provided circuit breaking rule.
     *
     * @param rule a valid circuit breaking rule
     * @return new circuit breaker based on provided rule; null if rule is invalid or unsupported type
     */
    static CircuitBreaker newCircuitBreakerFrom(/*@Valid*/ DegradeRule rule) {
        switch (rule.getGrade()) {
            case RuleConstant.DEGRADE_GRADE_RT:
                return new ResponseTimeCircuitBreaker(rule);
            case RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO:
            case RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT:
                return new ExceptionCircuitBreaker(rule);
            default:
                return null;
        }
    }

    public static boolean isValidRule(DegradeRule rule) {
        boolean baseValid = rule != null && !StringUtil.isBlank(rule.getResource())
                && rule.getCount() >= 0 && rule.getTimeWindow() > 0;
        if (!baseValid) {
            return false;
        }
        if (rule.getMinRequestAmount() <= 0 || rule.getStatIntervalMs() <= 0) {
            return false;
        }
        switch (rule.getGrade()) {
            case RuleConstant.DEGRADE_GRADE_RT:
                return rule.getSlowRatioThreshold() >= 0 && rule.getSlowRatioThreshold() <= 1;
            case RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO:
                return rule.getCount() <= 1;
            case RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT:
                return true;
            default:
                return false;
        }
    }

    private static CircuitBreaker createNewOrReuseCircuitBreaker(String resource,DegradeRule degradeRule, List<CircuitBreaker> circuitBreakerList){
        if(circuitBreakerList ==null){
            return newCircuitBreakerFrom(SentinelXConfig.isEnableCopy() ? DegradeCopy.replace(resource, degradeRule): degradeRule);
        }
        for (CircuitBreaker circuitBreaker : circuitBreakerList) {
            if (ruleEquals(degradeRule, circuitBreaker.getRule())) {
               return circuitBreaker;
            }
        }
        return newCircuitBreakerFrom(SentinelXConfig.isEnableCopy() ? DegradeCopy.replace(resource, degradeRule): degradeRule);
    }

    public static Object getBackRules() {
        return circuitBreakerBack;
    }

    private static class RulePropertyListener implements PropertyListener<List<DegradeRule>> {

        private synchronized void reloadFrom(List<DegradeRule> list) {
            long stamp = lock.writeLock();
            try {
                if (list == null || list.size() == 0) {
                    circuitBreakerBack.clear();
                    degradeRules.clear();
                    return;
                }

                Map<String, List<CircuitBreaker>> defaultCbMap = new ConcurrentHashMap<>();
                for (Map.Entry<String, List<CircuitBreaker>> kv : circuitBreakerBack.entrySet()) {
                    List<CircuitBreaker> circuitBreakers = kv.getValue();
                    List<CircuitBreaker> defaultCbList = new ArrayList<>();
                    for (DegradeRule degradeRule : list) {
                        if (!isValidRule(degradeRule)) {
                            RecordLog.warn("[DefaultDegradeManager] Ignoring invalid rule when loading new rules: " + degradeRule);
                            continue;
                        }

                        if (StringUtil.isBlank(degradeRule.getLimitApp())) {
                            degradeRule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT);
                        }

                        defaultCbList.add(createNewOrReuseCircuitBreaker(kv.getKey(),degradeRule,circuitBreakers));
                    }
                    defaultCbMap.put(kv.getKey(), defaultCbList);
                }

                circuitBreakerBack.clear();
                circuitBreakerBack.putAll(defaultCbMap);

                degradeRules.clear();
                degradeRules.addAll(list);
            } finally {
                lock.unlockWrite(stamp);
            }


        }

        @Override
        public void configUpdate(List<DegradeRule> conf) {
            reloadFrom(conf);
            RecordLog.info("[DefaultDegradeManager] Degrade rules has been updated to: " + degradeRules);
        }

        @Override
        public void configLoad(List<DegradeRule> conf) {
            reloadFrom(conf);
            RecordLog.info("[DefaultDegradeManager] Degrade rules loaded: " + degradeRules);
        }

    }


    private static boolean ruleEquals(DegradeRule rule1, DegradeRule rule2) {
        if (rule1 == rule2) {
            return true;
        }

        if (!StringUtil.equals(rule1.getLimitApp(), rule2.getLimitApp())) {
            return false;
        }

        return Double.compare(rule1.getCount(), rule2.getCount()) == 0 &&
                rule1.getTimeWindow() == rule2.getTimeWindow() &&
                rule1.getGrade() == rule2.getGrade() &&
                rule1.getMinRequestAmount() == rule2.getMinRequestAmount() &&
                Double.compare(rule1.getSlowRatioThreshold(), rule2.getSlowRatioThreshold()) == 0 &&
                rule1.getStatIntervalMs() == rule2.getStatIntervalMs();
    }
}
